PD-L1 IHC staining and scoring method

SK006 DAKO RTU on AutostainerLink48 PD-L1 IHC 22C3 pharmDx for Autostainer (SK006): https://www.agilent.com/en/product/pharmdx/pd-l1-ihc-22c3-pharmdx/pd-l1-ihc-22c3-pharmdx-for-autostainer-link-48-1760224

See notes: https://pdl1.azurewebsites.net Tilsinbreastcancer.org

This is the same antibody as used in the KEYNOTE-355 study (Pembrolizumab plus chemotherapy versus placebo plus chemotherapy for previously untreated locally recurrent inoperable or metastatic triple-negative breast cancer)

In the KEYNOTE_355 trial, a survival benefit of recieving pembrolizumab is shown for mTNBC with CPS score >=10. CPS = Combined Positive Score.

CPS score = (Positive Tumor cells + positive immune cells / Total number of tumor cells) * 100

Immune cells = lymphocytes and macrophages.

This gives a CPS score that theoretically can be more than 100.

A minimum of 100 viable tumor cells in the PD⁠-⁠L1–stained slide is required for the specimen to be considered adequate for PD⁠-⁠L1 evaluation.

37% of TNBC has CPS >= 10 in the KEYNOTE_355 trial.

CPS calculation:

positiveImmuneCells = 851
positiveTumorCells = 1005
negativeTumorCells = 21219
(TumorCells = positiveTumorCells+negativeTumorCells)
## [1] 22224
(CPS = ((positiveImmuneCells+positiveTumorCells)/TumorCells)*100)
## [1] 8.351332

Background on Cell classification using QuPath :

From the QuPath paper:

Analysis of PD-L1 IHC The approach to scoring PD-L1 was similar to p53, apart from the following: 1) a three-way random trees classifier was trained to distinguish between epithelial, non-epithelial and ‘other’ detections (including artefacts and necrosis); 2) cells were classified as positive or negative based upon a single intensity threshold applied to the maximum DAB optical density within the full cell area, and 3) summary scores were generated as the percentage of cells classified as positive, with ‘other’ detections removed.

Analysis of p53 IHC Preprocessing steps were applied as described above. QuPath’s Cell detection command was then used to identify cells across all cores based upon nuclear staining. This command additionally estimates the full extent of each cell based upon a constrained expansion of the nucleus region, and calculates up to 33 measurements of intensity and morphology, including nucleus area, circularity, staining intensity for hematoxylin and DAB, and nucleus/cell area ratio. Because not all of these measurements are expected to provide independent or useful information with regard to cell classification, a subset of 16 measurements was chosen empirically and supplemented for each cell by measuring the local density of cells, and taking a Gaussian-weighted sum of the corresponding measurements within neighboring cells using QuPath’s Add smoothed features command. A two-way random trees classifier was then interactively trained to distinguish tumor epithelial cells from all other detections (comprising non-epithelial cells, necrosis, or any artefacts misidentified as cells) and applied across all slides (see Supplementary Video 2). Intensity thresholds were set to further subclassify tumor cells as being negative, weak, moderate or strongly positive for p53 staining based upon mean nuclear DAB optical densities. An H-score was calculated for each tissue core by adding 3x% strongly stained tumor nuclei, 2x% moderately stained tumor nuclei, and 1x% weakly stained tumor nuclei32, giving results in the range 0 (all tumor nuclei negative) to 300 (all tumor nuclei strongly positive).

Creating a PDL1 positive cell detection and cell classification Workflow:

Create QuPath project-folder “PDL1_train”

Import 18 randomly selected images from PETREMAC arms A, B, E, F, G, H.

Estimate stain vector

Estimate Stain vector for each protein staining.

Estimate stain vector

Image used for stain vector: 2022-08-06 15.37.15.ndpi

Create Script “PDL1_EstimateStainVector.groovey”

setImageType('BRIGHTFIELD_H_DAB'); 
setColorDeconvolutionStains('{"Name" : "H-DAB-PDL1", "Stain 1" :
"Hematoxylin", "Values 1" : "0.77475 0.55533 0.30229", "Stain 2" :
"DAB", "Values 2" : "0.20981 0.43913 0.87358", "Background" : " 232 233
233 "}');

Shortcut for Script editor: Command+L, search for script.

Positive cell detection

Select appropriate settings for positive cell detection.
PD-L1 stains the membrane. There is no current reliable/easy method for selecting membrane staining in QuPath, so the most practical method is to set the max intensity for the cell as threshold for positive cell detection.

– Intensity threshold parameters > Score compartment > Cell: DAB OD max

Select an appropriate threshold that balances detection of false positives and false negatives among the samples in the training set with the selected stain vector.

Tested positive cell detection on negative slides, positive slides and slides with artefacts. Standard settings, Score compartment Cell: DAB OD max single threshold 0.45 seemed the most reasonable balance of picking up weak positive, and false positives from background. Worked well with strong staining too.

Cell detection standard settings, Cell: DAB OD max. Intensity threshold was set as 0.45.

Positive Cell detection settings

Go to workflow, and save as a script.

Run positive cell detection and save script from workflow as “PositiveCellDetectionPDL1.groovey”

runPlugin('qupath.imagej.detect.cells.PositiveCellDetection', '{"detectionImageBrightfield": "Hematoxylin OD", "requestedPixelSizeMicrons": 0.5, "backgroundRadiusMicrons": 8.0, "medianRadiusMicrons": 0.0, "sigmaMicrons": 1.5, "minAreaMicrons": 10.0, "maxAreaMicrons": 500.0, "threshold": 0.1, "maxBackground": 2.0, "watershedPostProcess": true, "excludeDAB": false, "cellExpansionMicrons": 5.0, "includeNuclei": true, "smoothBoundaries": true, "makeMeasurements": true, "thresholdCompartment": "Cell: DAB OD max", "thresholdPositive1": 0.45, "thresholdPositive2": 0.4, "thresholdPositive3": 0.6000000000000001, "singleThreshold": true}');

QuPath splits the annotated area into tiles to not overload memory, and runs the cell detection tile by tile for larger areas:

This gives us a lot of measurements, which can be found by clicking the Measurement table icon. The Nucleus/Cell area ratio gives a good pinpoint to whether each cell is a tumor cell or not (Size and density), but all other measurements are also taken into account when classifying cells.

Add smooth features

Add smooth to cell detection: -> Analyze -> Calculate features -> Add smooth features

In “Smooth object features” window select Radius (FWHM) = 50 um, and do not tick the box “Smooth within class”.

From the QuPath docs:

The weighting depends on distance, i.e. cells that are further away have less contribution to the result. Technically, distance is based on centroids and the weighting is calculated from a Gaussian function, where the parameter required in the dialog box is the full-width-at-half-maximum of the Gaussian function. Less technically, putting higher numbers into the dialog box results in more smoothing. This reduces the noisiness of the measurements more effectively, but also makes it more difficult to distinguish smaller areas containing particular cell types.

This will supplement all the measurements with the weighted sum of the corresponding measurements of the neighbouring cells, smooth out all the measurements Although tumor cells will correspond well with the Nucleus/cell area, no single measurement is good enough, so we will rely on machine learning to classify the detected cells into “Tumor” (Epithelial like) and “Stroma” .

Smooth features

In short: Smooth features to help classification algorithm.

Run as script in script editor:

runPlugin('qupath.lib.plugins.objects.SmoothFeaturesPlugin', {"fwhmMicrons": 50.0, "smoothWithinClasses": false}');

Note; Issue with samples with weak nuclear blue staining, cell detection does not detect all cells.

Classify cells

Add Tumor/Stroma/immune cell annotation

Use brush-tool or wand/polygon tool to annotate an area - Control-click -> Set class

Annotate Tumor/Immune/ignore

Repeat for all images in PDL1_train project.

Add - Tumor - Immune cell - Ignore (Red blood cells, debree, necrosis, artefacts etc) annotations.

Open the Train Object Classifier with live update. Select all images in project for training. Do a manual check for correct annotations, and add more where needed

-> Classify -> Object classification -> Train object classifier Live update Object filter : Detections (all) Classifier : Random trees (RTrees) Classes : All classes Training : Unlocked annotations

Save classifier when satisfied, and load classifier in stead of train when classifying images in a new project.

  • Used the annotated 18 randomly selected images from all arms A, B, E, F, G, H.

Saved classifier as “PDL1_TumorImmuneIgnore_cellClassifier.json” in the project folder:

-> classifiers -> object_classifiers

Run Object classifier

A PD-L1 negative tumor:

A PD-L1 positive tumor:

Note on artefacts:

Export Cell Count Report

For single images, click the table image, select annotation and copy to clipboard:

For projects, use the “Export measurements”.:

-Measurements > Detections: Exports ALL data pr cell measurements. Time consuming! -Measurements > Annotations: Export summarized measurements in annotations. Fast.

Export detections:

Will export every measurement on cell-level. Exporting an entire project at once will take some time, so time the export for non-working sessions.

Export Annotations:

Will export summarized measurements in annotations. Not too time-consuming, and the selection needed for calculationg CPS score.

Workflow for PD-L1 CPS score

Import images

Since the images are BIG and we have 200 images, split the project into 8 prjojects a 25 samples. Used random number generator to select 25 images for each project in the Test_Path_concat_imagename.xlsx file. Use excelfile to copy file location of the first 25, then the next etc.

  • Make a new folder in the main PDL1count folder and create new QuPath project in the empty folder
  • Copy the PDL1_TumorImmuneCell_Ignore_Classifier.json classifier to the projects > classifiers > object_classifiers -folder.
  • Keep the scripts in the scripts folder for the PDL1 main project.
  • Add images by copying the images file path from Test_Path_concat_imagename.xlsx.

Estimate Stain vector

Run for Project :

Script: PDL1_EstimateStainVector.groovy

setImageType('BRIGHTFIELD_H_DAB');
setColorDeconvolutionStains('{"Name" : "H-DAB-PDL1", "Stain 1" : "Hematoxylin", "Values 1" : "0.77475 0.55533 0.30229 ", "Stain 2" : "DAB", "Values 2" : "0.20981 0.43913 0.87358 ", "Background" : " 232 233 233 "}');

Slide QC check, Annotate area

Use manual selection with wand tool/ brush. Remove areas with artefacts (necrosis, dust i.e.) and normal tissue. Check slide for proper staining or artefacts.

  • Check integrity of slide, size and if enough tumor area.

  • Check DAB staining and artefacts. PD-L1 should stain membrane and not cytoplasm/nucleus.

  • Annotate area, excluding areas with normal tissue, artefacts and necrosis.

Detect cells

Run for Project:

Script: PositiveCellDetectionPDL1_w_CellClassificationTumorImmuneOther.groovy

selectAnnotations() ;
runPlugin('qupath.imagej.detect.cells.PositiveCellDetection', '{"detectionImageBrightfield": "Hematoxylin OD",  "requestedPixelSizeMicrons": 0.5,  "backgroundRadiusMicrons": 8.0,  "medianRadiusMicrons": 0.0,  "sigmaMicrons": 1.5,  "minAreaMicrons": 10.0,  "maxAreaMicrons": 500.0,  "threshold": 0.1,  "maxBackground": 2.0,  "watershedPostProcess": true,  "excludeDAB": false,  "cellExpansionMicrons": 5.0,  "includeNuclei": true,  "smoothBoundaries": true,  "makeMeasurements": true,  "thresholdCompartment": "Cell: DAB OD max",  "thresholdPositive1": 0.45,  "thresholdPositive2": 0.4,  "thresholdPositive3": 0.6000000000000001,  "singleThreshold": true}');
runPlugin('qupath.lib.plugins.objects.SmoothFeaturesPlugin', '{"fwhmMicrons": 50.0,  "smoothWithinClasses": false}');
runObjectClassifier("PDL1_TumorImmuneIgnore_cellClassifier");

Export results

Export pr project Annotations in .tsv.

Pay attention to column order when concatenating files, they are not necessarily in the same order!

Calculate CPS.

positiveImmuneCells = 851
positiveTumorCells = 1005
negativeTumorCells = 21219
(TumorCells = positiveTumorCells+negativeTumorCells)
## [1] 22224
(CPS = ((positiveImmuneCells+positiveTumorCells)/TumorCells)*100)
## [1] 8.351332

Results Examples:

CPS score = 0

CPS score between 1-10:

CPS score more than 10: